package com.orange.wwwapi.service.carpark;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Joiner;
import com.orange.core.mapper.user.UserMapper;
import com.orange.core.pojo.entity.carpark.CarParkOrder;
import com.orange.core.pojo.entity.credit.CreditFlow;
import com.orange.core.pojo.entity.user.User;
import com.orange.core.pojo.entity.user.UserCar;
import com.orange.core.pojo.enums.EFlowBusinessType;
import com.orange.core.pojo.enums.EFlowType;
import com.orange.core.service.carpark.CarParkOrderService;
import com.orange.core.service.credit.CreditFlowService;
import com.orange.core.util.Constants;
import com.orange.wwwapi.client.KeyTopCarParkClient;
import com.orange.wwwapi.config.KeyTopCarParkProperty;
import com.orange.wwwapi.pojo.req.carpark.keytop.GetParkingPaymentInfoReq;
import com.orange.wwwapi.pojo.req.carpark.keytop.KeyTopBaseReq;
import com.orange.wwwapi.pojo.req.carpark.keytop.PayParkingFeeReq;
import com.orange.wwwapi.pojo.rsp.carpark.keytop.GetParkingPaymentInfoRsp;
import com.orange.wwwapi.pojo.rsp.carpark.keytop.KeyTopBaseRsp;
import com.orange.wwwapi.pojo.rsp.carpark.keytop.PayParkingFeeRsp;
import com.orange.wwwapi.pojo.rsp.user.UserCarParkFeeRsp;
import com.orange.wwwapi.service.user.UserCarService;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DurationFormatUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;

@Slf4j
@RequiredArgsConstructor
@ConditionalOnBean(KeyTopCarParkProperty.class)
@Service
public class KeyTopCarParkService {

    private static final String CAR_PARK_FEE_KEY = "car_park_fee:%s:%s";

    private final UserMapper userMapper;
    private final UserCarService userCarService;
    private final CarParkOrderService carParkOrderService;
    private final CreditFlowService creditFlowService;
    private final KeyTopCarParkClient keyTopCarParkClient;
    private final KeyTopCarParkProperty keyTopCarParkProperty;
    private final ObjectMapper objectMapper;
    private final StringRedisTemplate redisTemplate;

    @SneakyThrows
    public UserCarParkFeeRsp getCarParkFee(String plateNo, int userId) {
        GetParkingPaymentInfoRsp parkingPaymentInfo;
        try {
            parkingPaymentInfo = getParkingPaymentInfo(plateNo);
        } catch (Exception e) {
            throw new IllegalArgumentException(e.getMessage());
        }
        Assert.notNull(parkingPaymentInfo, "查询停车费用失败");

        BigDecimal payable = new BigDecimal(parkingPaymentInfo.getPayable()).divide(Constants.ONE_HUNDRED, 2, RoundingMode.HALF_UP);
        int deductibleCredit = 0;
        BigDecimal deductibleAmount = BigDecimal.ZERO;
        if (payable.compareTo(BigDecimal.ZERO) > 0) {
            // 获取用户信息
            User user = userMapper.selectById(userId);
            // 计算用户积分可以兑换的最大金额
            int maxExchangeAmount = Math.min(user.getCredit() / 50 * 3, 48);  // 每50积分可兑换3元，最大可兑换48元
            deductibleAmount = new BigDecimal(maxExchangeAmount);
            if (payable.compareTo(deductibleAmount) < 0) {
                deductibleAmount = payable.divide(Constants.THREE, 0, RoundingMode.CEILING).multiply(Constants.THREE);
                maxExchangeAmount = deductibleAmount.intValue();
            }

            // 计算需要扣除的积分
            if (maxExchangeAmount > 0) {
                deductibleCredit = maxExchangeAmount / 3 * 50;
            }
        }

        UserCarParkFeeRsp rsp = UserCarParkFeeRsp.builder()
                .userId(userId)
                .plateNo(plateNo)
                .parkName(keyTopCarParkProperty.getParkName())
                .parkDuration(DurationFormatUtils.formatDuration(parkingPaymentInfo.getElapsedTime() * 60 * 1000, "HH时mm分钟ss秒"))
                .parkOrderNo(parkingPaymentInfo.getOrderNo())
                .entryTime(parkingPaymentInfo.getEntryTime())
                .totalAmount(payable.stripTrailingZeros())
                .deductibleCredit(deductibleCredit)
                .deductibleAmount(deductibleAmount)
                .deductAll(deductibleAmount.compareTo(payable) >= 0)
                .build();

        String key = String.format(CAR_PARK_FEE_KEY, userId, plateNo);
        redisTemplate.opsForValue().set(key, objectMapper.writeValueAsString(rsp));
        redisTemplate.expire(key, 10, TimeUnit.MINUTES);

        return rsp;
    }

    @SneakyThrows
    @Transactional
    public Integer payParkingFee(String plateNo, int userId) {
        String key = String.format(CAR_PARK_FEE_KEY, userId, plateNo);
        String str = redisTemplate.opsForValue().get(key);
        Assert.notBlank(str, "停车信息已超时，请重新查询");

        UserCarParkFeeRsp userCarParkFeeRsp = objectMapper.readValue(str, UserCarParkFeeRsp.class);
        User user = userMapper.selectById(userId);
        Assert.isTrue(userCarParkFeeRsp.getDeductibleCredit() <= user.getCredit());

        Assert.isTrue(userMapper.deductCredit(userId, userCarParkFeeRsp.getDeductibleCredit()), "扣减积分失败");

        UserCar userCar = userCarService.lambdaQuery()
                .eq(UserCar::getUserId, userId)
                .eq(UserCar::getPlateNo, plateNo)
                .one();

        CarParkOrder carParkOrder = new CarParkOrder()
                .setUserId(userId)
                .setUserCarId(userCar.getId())
                .setPlateNo(plateNo)
                .setParkName(keyTopCarParkProperty.getParkName())
                .setParkOrderNo(userCarParkFeeRsp.getParkOrderNo())
                .setEntryTime(LocalDateTimeUtil.parse(userCarParkFeeRsp.getEntryTime(), DatePattern.NORM_DATETIME_FORMATTER))
                .setParkDuration(userCarParkFeeRsp.getParkDuration())
                .setTotalAmount(userCarParkFeeRsp.getTotalAmount())
                .setPaidCredit(userCarParkFeeRsp.getDeductibleCredit())
                .setDeductAmount(userCarParkFeeRsp.getDeductibleAmount())
                .setCouponSolutionNo(userCarParkFeeRsp.getCouponSolutionNo());
        carParkOrderService.save(carParkOrder);

        creditFlowService.save(new CreditFlow()
                .setUserId(userId)
                .setBusinessId(carParkOrder.getId())
                .setBusinessType(EFlowBusinessType.PARKING_FEE.getValue())
                .setFlowType(EFlowType.CONSUME.getValue())
                .setCredit(-userCarParkFeeRsp.getDeductibleCredit()));

        PayParkingFeeRsp payParkingFeeRsp;
        try {
            payParkingFeeRsp = payParkingFee(userCarParkFeeRsp);
        } catch (Exception e) {
            throw new IllegalArgumentException(e.getMessage());
        } finally {
            redisTemplate.delete(key);
        }

        return payParkingFeeRsp.getParkingTime();
    }

    @SneakyThrows
    private GetParkingPaymentInfoRsp getParkingPaymentInfo(String carNo) {
        GetParkingPaymentInfoReq req = new GetParkingPaymentInfoReq();
        setBaseReq(req, "getParkingPaymentInfo");
        req.setPlateNo(carNo);
        req.setKey(generateKey(req));
        KeyTopBaseRsp<String> rsp = keyTopCarParkClient.getParkingPaymentInfo(keyTopCarParkProperty.getBaseUrl(), req);
        return objectMapper.readValue(rsp.check(), GetParkingPaymentInfoRsp.class);
    }

    @SneakyThrows
    private PayParkingFeeRsp payParkingFee(UserCarParkFeeRsp userCarParkFeeRsp) {
        PayParkingFeeReq req = new PayParkingFeeReq();
        setBaseReq(req, "payParkingFee");
        req.setOrderNo(userCarParkFeeRsp.getParkOrderNo());
        req.setPayableAmount(userCarParkFeeRsp.getTotalAmount().multiply(Constants.ONE_HUNDRED).intValue());
        req.setAmount(userCarParkFeeRsp.getDeductibleAmount().multiply(Constants.ONE_HUNDRED).intValue());
        req.setPayType(1010);
        req.setPayMethod(1013);
        req.setFreeMoney(0);
        req.setFreeTime(0);
        if (req.getFreeMoney() > 0) {
            req.setFreeDetail(objectMapper.writeValueAsString(Collections.singletonList(new PayParkingFeeReq.FreeDetail()
                    .setMoney(req.getFreeMoney())
                    .setTime(0)
                    .setCode(userCarParkFeeRsp.getUserId().toString())
                    .setType(0))));
        }
        req.setKey(generateKey(req));
        KeyTopBaseRsp<String> rsp = keyTopCarParkClient.payParkingFee(keyTopCarParkProperty.getBaseUrl(), req);
        return objectMapper.readValue(rsp.check(), PayParkingFeeRsp.class);
    }

    private void setBaseReq(KeyTopBaseReq req, String serviceCode) {
        req.setAppId(keyTopCarParkProperty.getAppId());
        req.setParkId(keyTopCarParkProperty.getParkId());
        req.setServiceCode(serviceCode);
        req.setTs(System.currentTimeMillis() + "");
        req.setReqId(RandomUtil.randomString(16));
    }

    @SneakyThrows
    private String generateKey(Object obj) {
        Map<String, String> params = new TreeMap<>();
        new JSONObject(obj).entrySet().stream()
                .filter(entry -> !"key".equals(entry.getKey())
                        && !"appId".equals(entry.getKey())
                        && entry.getValue() != null
                        && !(entry.getValue() instanceof Map)
                        && !(entry.getValue() instanceof Iterable))
                .forEach(entry -> {
                    if (!entry.getValue().equals("")) {
                        params.put(entry.getKey(), entry.getValue().toString());
                    }
                });
        String signStr = Joiner.on("&").withKeyValueSeparator("=").join(params).concat("&").concat(keyTopCarParkProperty.getAppSecret());
        return DigestUtil.md5Hex(signStr).toUpperCase();
    }
}
